package actionsvc

import (
	"context"
	"database/sql"
	"encoding/base64"
	"fmt"
	"html/template"
	"strconv"
	"strings"

	new_admin "code.bydev.io/cht/customer/kyc-stub.git/pkg/bybit/kyc/admin/v1"
	"code.bydev.io/cht/customer/kyc-stub.git/pkg/bybit/kyc/common/enums"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/awss3"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/biz"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/helper"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycadminapi"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycadminapi/dto"
	ka_dto "code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycadminrepo/dto"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycadminrepo/o_kyc_action_state"
	pvd_dto "code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycprovider/dto"
	kycprovider "code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycprovider/provider"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/utils/timeutil"
	"code.bydev.io/cht/customer/kyc_dbstructure.git/pkg/o_kyc_country_config"
	"code.bydev.io/frameworks/sechub-go/api"
	"git.bybit.com/svc/go/pkg/bzap"
	kycadmin_api "git.bybit.com/svc/stub/pkg/pb/api/kycadmin"
	jsoniter "github.com/json-iterator/go"
	"go.uber.org/zap"
)

type ActionSvc = *action
type action struct {
	secHub *api.Sechub
	repo   biz.KycAdminRepo
	logger *zap.Logger
	teller kycadminapi.Teller
}

const (
	ImgTokenTTLMs  int64  = 180000
	ImgDownloadURL string = "kycadmin/image/download?token=%s"
)

func New(repo biz.KycAdminRepo, secHub *api.Sechub, logger *zap.Logger, teller kycadminapi.Teller) ActionSvc {
	return &action{
		repo:   repo,
		secHub: secHub,
		logger: logger,
		teller: teller,
	}
}

func (a *action) GetActionList(ctx context.Context, request *dto.ActionListCondition) ([]*o_kyc_action_state.Record, int64, error) {

	return a.repo.GetActionList(ctx, request)
}

func (a *action) GetActionDetail(ctx context.Context, request *dto.ActionDetailCondition) (*kycadmin_api.KycActionDetailResponse, error) {
	record, err := a.repo.GetActionDetail(ctx, request)
	if err != nil {
		bzap.LogError(
			a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request)),
			"GetActionDetail DB error", err)

		return nil, err
	}
	if record == nil {
		a.logger.Info("No records", zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request))
		return nil, nil
	}

	resp := new(kycadmin_api.KycActionDetailResponse)
	cd := &ka_dto.ActionStateClientDetail{ClientIP: "", From: "", Source: ""}
	if record.ClientDetail != "" {
		err = jsoniter.UnmarshalFromString(record.ClientDetail, cd)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("ClientDetail", record.ClientDetail)),
				"json unmarshal ClientDetail error", err)
		}
	}

	ad := &ka_dto.ActionStateAddressDetail{Country: "", State: "", City: "", FullAddress: "", PostalCode: ""}
	if record.AddressDetail != "" {
		// Sechub 解密 AddressDetail
		record.AddressDetail, err = helper.DeAES256CodeString(a.secHub, record.AddressDetail)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Int64("id", record.Id)),
				"AES256 decrypt AddressDetail error", err)
		}

		err = jsoniter.UnmarshalFromString(record.AddressDetail, ad)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Int64("id", record.Id)),
				"json unmarshal AddressDetail error", err)
		}
	}

	dd := &ka_dto.ActionStateDocDetail{DocFront: "", DocBack: "", PersonalNumber: ""}
	if record.DocDetail != "" {
		// Sechub 解密 DocDetail
		record.DocDetail, err = helper.DeAES256CodeString(a.secHub, record.DocDetail)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request)),
				"DES256 decrypt DocDetail error", err)
		}
		err = jsoniter.UnmarshalFromString(record.DocDetail, dd)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request)),
				"json unmarshal DocDetail error", err)
		}
	}

	fd := &ka_dto.ActionStateFaceDetail{FaceImage: "", LivenessImage: "", SimilarityChecked: "", ValidityChecked: ""}
	if record.FaceDetail != "" {
		// Sechub 解密 FaceDetail
		record.FaceDetail, err = helper.DeAES256CodeString(a.secHub, record.FaceDetail)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request)),
				"DES256 decrypt FaceDetail error", err)
		}

		err = jsoniter.UnmarshalFromString(record.FaceDetail, fd)
		if err != nil {
			bzap.LogError(
				a.logger.With(zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request)),
				"json unmarshal FaceDetail error", err)
		}
	}

	resp.Id = record.Id
	t := timeutil.GetMillisecond()
	tokenFnt, _ := helper.EnAES256CodeString(a.secHub, fmt.Sprintf("%d|%d|%s", t, record.Id, "FRONT"))
	tokenBak, _ := helper.EnAES256CodeString(a.secHub, fmt.Sprintf("%d|%d|%s", t, record.Id, "BACK"))
	tokenFce, _ := helper.EnAES256CodeString(a.secHub, fmt.Sprintf("%d|%d|%s", t, record.Id, "FACE"))

	// 因为图片内容加密，所以需要返回图片下载链接
	frontImgURL := fmt.Sprintf(ImgDownloadURL, base64.RawStdEncoding.EncodeToString([]byte(tokenFnt)))
	backImgURL := fmt.Sprintf(ImgDownloadURL, base64.RawStdEncoding.EncodeToString([]byte(tokenBak)))
	faceImgURL := fmt.Sprintf(ImgDownloadURL, base64.RawStdEncoding.EncodeToString([]byte(tokenFce)))

	resp.ProviderDecision = pvd_dto.GetProviderDecisionEnDesc(record.ProviderDecision)
	resp.Images = &kycadmin_api.KycActionDetailImagesItem{
		DocFront:  frontImgURL,
		DocBack:   backImgURL,
		FaceImage: faceImgURL,
	}
	var (
		reason, validity, similarity string
	)
	switch pvd_dto.ReviewResult(record.Decision) {
	case pvd_dto.ReviewResultPass:
		reason = pvd_dto.ReviewResultEnPass.String()
		validity = fd.ValidityChecked
		similarity = fd.SimilarityChecked
	case pvd_dto.ReviewResultReject:
		if record.RejectReason == "" && record.ProviderRejectReason == "" {
			reason = pvd_dto.ReviewResultEnReject.String()
		} else {
			// 优先展示中台结果
			if record.RejectReason != "" {
				reason = record.RejectReason
			} else {
				reason = record.ProviderRejectReason
			}
		}
		validity = fd.ValidityChecked
		similarity = fd.SimilarityChecked
	case pvd_dto.ReviewResultUnknown:
		// 认证中是显示未知状态
		reason = pvd_dto.ReviewResultEnUnknown.String()
	}
	resp.BizDecision = &kycadmin_api.KycActionDetailBizDecisionItem{
		MemberId: record.MemberId,
		Decision: reason,
	}
	resp.EventData = &kycadmin_api.KycActionDetailEventItem{
		ExternalUid:      record.ExternalUid,
		RecordId:         record.RecordId,
		Source:           cd.Source,
		ClientIp:         cd.ClientIP,
		From:             cd.From,
		ProviderCreateAt: record.ProviderCreateAt,
	}
	resp.FaceMatch = &kycadmin_api.KycActionDetailFaceMatchItem{
		Similarity: similarity,
		Validity:   validity,
	}
	// 加密字段解密
	fnDe, _ := helper.DeAES256CodeString(a.secHub, record.FirstName)
	lnDe, _ := helper.DeAES256CodeString(a.secHub, record.LastName)
	dobDe, _ := helper.DeAES256CodeString(a.secHub, record.Dob)
	nato := record.Nationality
	if nato == "" {
		nato = record.Country
	}

	resp.UserData = &kycadmin_api.KycActionDetailUserItem{
		FirstName:        fnDe,
		LastName:         lnDe,
		Bob:              dobDe,
		City:             ad.City,
		Nationality:      helper.CountryNameAlpha3(nato).CnName,
		PostalCode:       ad.PostalCode,
		FormattedAddress: ad.FullAddress,
	}
	subDtDesc := ""
	if record.DocSubtype > 0 {
		subDtDesc = kycprovider.GetDocTypeDesc(pvd_dto.KYCProvider(record.Channel), pvd_dto.DocType(record.DocSubtype))
	}
	dtdesc := kycprovider.GetDocTypeDesc(pvd_dto.KYCProvider(record.Channel), pvd_dto.DocType(record.DocType))
	if dto.Decision(record.Decision).IsBizSuccess() && pvd_dto.KYCProvider(record.Channel).IsProviderSumSub() && record.DocType == 0 { // 兼容旧居住证错误数据
		dtdesc = pvd_dto.DocumentTypeResidence.String()
	}

	// 加密存储字段解密
	record.DocNumber, _ = helper.DeAES256CodeString(a.secHub, record.DocNumber)

	resp.CardData = &kycadmin_api.KycActionDetailCardItem{
		DocType:        dtdesc,
		DocSubtype:     subDtDesc,
		DocNumber:      record.DocNumber,
		PersonalNumber: dd.PersonalNumber,
		ExpireDate:     timeutil.ToStringYmd(record.ExpireDate),
		IssuingDate:    timeutil.ToStringYmd(record.IssuingDate),
		Country:        helper.CountryNameAlpha3(record.Country).CnName,
	}
	resp.IsTransfer = record.IsTransfer > 0

	switch record.IsTransfer {
	case 1:
		transChain, errChain := a.repo.GetTarTransferChain(ctx, record.MemberId, record.IdempotentId)
		if errChain != nil {
			return resp, errChain
		}
		resp.TransChain = transChain
	case 2:
		transChain, errChain := a.repo.GetSrcTransferChain(ctx, record.MemberId, record.IdempotentId)
		if errChain != nil {
			return resp, errChain
		}
		resp.TransChain = transChain
	}

	a.logger.Info("response", zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("request", request))

	return resp, err
}

func (a *action) GetActionImage(ctx context.Context, token string) ([]byte, int, error) {
	tokenDe, err := helper.DeAES256CodeString(a.secHub, token)
	if err != nil {
		bzap.LogError(
			a.logger.With(
				zap.Any("reqID", helper.GetReqID(ctx)),
				zap.Any("token", token),
			),
			"AES256 decode token error",
			err,
		)
		return nil, 0, err
	}
	tkns := strings.Split(tokenDe, "|")
	if len(tkns) != 3 {
		bzap.LogError(
			a.logger.With(
				zap.Any("reqID", helper.GetReqID(ctx)),
				zap.Any("token", token),
				zap.Any("len", len(tkns)),
			),
			"length error",
			err,
		)
		return nil, 0, err
	}
	tt, err := strconv.ParseInt(tkns[0], 10, 64)
	if err != nil {
		bzap.LogError(
			a.logger.With(
				zap.Any("reqID", helper.GetReqID(ctx)),
				zap.Any("token", token),
			),
			"Parse param 1st not int",
			err,
		)
		return nil, 0, err
	}

	id, err := strconv.ParseInt(tkns[1], 10, 64)
	if err != nil {
		bzap.LogError(
			a.logger.With(
				zap.Any("reqID", helper.GetReqID(ctx)),
				zap.Any("token", token),
			),
			"Parse param 2nd error",
			err,
		)
		return nil, 0, err
	}

	t := timeutil.GetMillisecond()
	// 180s 后过期
	if (t - tt) > ImgTokenTTLMs {
		a.logger.Info("Token expired",
			zap.Any("reqID", helper.GetReqID(ctx)),
			zap.Any("token", token),
		)

		return nil, 0, err
	}

	req := &dto.ActionDetailCondition{ID: id}
	record, err := a.repo.GetActionDetail(ctx, req)
	if err != nil {
		bzap.LogError(
			a.logger.With(
				zap.Any("reqID", helper.GetReqID(ctx)),
				zap.Any("token", token),
			),
			"DB error",
			err,
		)

		return nil, 0, err
	}

	if record == nil {
		a.logger.Info("No records", zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("token", token))

		return nil, 0, nil
	}

	docContent := ""
	docTp := template.HTMLEscapeString(tkns[2])

	switch docTp {
	case pvd_dto.ImageTagBack, pvd_dto.ImageTagFront:
		dd := &ka_dto.ActionStateDocDetail{DocFront: "", DocBack: "", PersonalNumber: ""}
		if record.DocDetail != "" {
			// Sechub 解密 DocDetail
			record.DocDetail, err = helper.DeAES256CodeString(a.secHub, record.DocDetail)
			if err != nil {
				bzap.LogError(
					a.logger.With(
						zap.Any("reqID", helper.GetReqID(ctx)),
						zap.Any("token", token),
						zap.Int64("id", record.Id),
					),
					"AES256 decrypt error",
					err)
			}
			err = jsoniter.UnmarshalFromString(record.DocDetail, dd)
			if err != nil {
				bzap.LogError(
					a.logger.With(
						zap.Any("reqID", helper.GetReqID(ctx)),
						zap.Any("token", token),
						zap.Int64("id", record.Id),
					),
					"json unmarshal DocDetail error",
					err)

				return nil, 0, err
			}
			if docTp == pvd_dto.ImageTagFront {
				docContent = dd.DocFront
			}
			if docTp == pvd_dto.ImageTagBack {
				docContent = dd.DocBack
			}
		}
	case pvd_dto.ImageTagLiveness, pvd_dto.ImageTagFace:
		fd := &ka_dto.ActionStateFaceDetail{FaceImage: "", LivenessImage: "", SimilarityChecked: "", ValidityChecked: ""}
		if record.FaceDetail != "" {
			// Sechub 解密 FaceDetail
			record.FaceDetail, err = helper.DeAES256CodeString(a.secHub, record.FaceDetail)
			if err != nil {
				bzap.LogError(
					a.logger.With(
						zap.Any("reqID", helper.GetReqID(ctx)),
						zap.Any("token", token),
						zap.Int64("id", record.Id),
					),
					"AES256 decode FaceDetail error",
					err)
			}
			err = jsoniter.UnmarshalFromString(record.FaceDetail, fd)
			a.logger.Debug("GetActionImage", zap.Any("record.FaceDetail", record.FaceDetail), zap.Any("fd", fd))
			if err != nil {
				bzap.LogError(
					a.logger.With(
						zap.Any("reqID", helper.GetReqID(ctx)),
						zap.Any("token", token),
						zap.Int64("id", record.Id),
					),
					"json unmarshal FaceDetail error",
					err)

				return nil, 0, err
			}
			if docTp == pvd_dto.ImageTagLiveness {
				docContent = fd.LivenessImage
			}
			if docTp == pvd_dto.ImageTagFace {
				docContent = fd.FaceImage
			}
		}
	}
	if docContent == "" {
		a.logger.Info("Doc content empty", zap.Any("reqID", helper.GetReqID(ctx)), zap.Any("token", token))

		return nil, 0, nil
	}

	// doc
	doc := &pvd_dto.Document{}
	err = jsoniter.UnmarshalFromString(docContent, doc)
	if err != nil {
		bzap.LogError(
			a.logger.With(
				zap.Any("reqID", helper.GetReqID(ctx)),
				zap.Any("token", token),
				zap.Int64("id", record.Id),
			),
			"json unmarshal docContent error",
			err)

		return nil, 0, err
	}

	dl := awss3.New(a.logger)
	a.logger.Debug("begin download", zap.Reflect("record", record), zap.Reflect("doc", doc))

	// KYC 转移时使用转移前ID
	hashID := record.IdempotentId
	if record.IsTransfer > 0 {
		ks := strings.Split(doc.Key, "/")
		hashID = ks[0]
	}

	return dl.Download(ctx, hashID, doc.Key)
}

func (a *action) GetCountryConfigList(ctx context.Context, condition *dto.CountrySearchCondition) ([]*new_admin.CountryConfig, int, error) {
	var configList []*new_admin.CountryConfig
	records, total, err := a.repo.GetCountryConfigList(ctx, condition)
	if err != nil {
		return configList, 0, err
	}

	for _, record := range records {
		countryConfig := &new_admin.CountryConfig{
			CountryCode:   record.CountryCode,
			CountryName:   record.CountryName,
			Provider:      enums.KYCProvider(record.Provider),
			CaptureMethod: enums.DocumentCaptureMethod(record.CaptureMethods),
		}
		var docTypes []enums.KYCDocType
		for _, docType := range strings.Split(record.DocAllowed, ",") {
			docTypeInt, _ := strconv.Atoi(docType)
			docTypes = append(docTypes, enums.KYCDocType(docTypeInt))
		}
		countryConfig.DocTypes = docTypes

		// 推荐类型
		var recDocTypes []enums.KYCDocType
		for _, docType := range strings.Split(record.DocRecommend, ",") {
			docTypeInt, _ := strconv.Atoi(docType)
			recDocTypes = append(recDocTypes, enums.KYCDocType(docTypeInt))
		}
		countryConfig.DocRecommend = recDocTypes
		countryConfig.SpecialTips = record.SpecialTips

		if record.ExtraData.Valid && record.ExtraData.String != "" {
			extraData := &dto.CountryConfigExtraData{}
			unmarshalErr := jsoniter.Unmarshal([]byte(record.ExtraData.String), extraData)
			if unmarshalErr != nil {
				a.logger.Error("Unmarshal Failed", zap.Any("reqID", helper.GetReqID(ctx)),
					zap.Reflect("record", record))
			} else {
				countryConfig.SpecialText = extraData.SpecialText
			}
		}

		configList = append(configList, countryConfig)
	}

	return configList, total, nil
}

func (a *action) SetCountryConfig(ctx context.Context, config *new_admin.CountryConfig) error {
	record := o_kyc_country_config.New()
	record.CountryCode = config.GetCountryCode()
	record.CountryName = config.GetCountryName()

	record.Provider = int8(config.GetProvider())
	record.CaptureMethods = int8(config.GetCaptureMethod())
	var docTypes []string
	for _, docType := range config.GetDocTypes() {
		docTypes = append(docTypes, strconv.Itoa(int(docType)))
	}
	var recDocTypes []string
	for _, docType := range config.GetDocRecommend() {
		recDocTypes = append(recDocTypes, strconv.Itoa(int(docType)))
	}

	record.DocAllowed = strings.Join(docTypes, ",")
	record.DocRecommend = strings.Join(recDocTypes, ",")
	record.SpecialTips = config.GetSpecialTips()

	if config.SpecialText != nil {
		extraData, err := jsoniter.Marshal(dto.CountryConfigExtraData{
			SpecialText: config.SpecialText,
		})
		if err != nil {
			return err
		}
		record.ExtraData = sql.NullString{
			String: string(extraData),
			Valid:  true,
		}
	}

	return a.repo.SetCountryConfig(ctx, record)
}

func (a *action) GetCommonConfig(ctx context.Context) (*dto.CommonConfig, error) {
	commonConfig := &dto.CommonConfig{}

	docSubtypeConfig, err := a.repo.GetDocSubtypeConfig(ctx)
	if err != nil {
		a.logger.Error("GetDocSubtypeConfig Failed", zap.Any("reqID", helper.GetReqID(ctx)))
	}

	commonConfig.DocSubtype = docSubtypeConfig

	return commonConfig, nil
}

func (a *action) SetCommonConfig(ctx context.Context, config *new_admin.CommonConfig) error {
	return a.repo.SetDocSubtypeConfig(ctx, config.DocSubtype)
}
